<?php
// 对象池模式（Pool）  
/**
 * 对象池模式是一种提前准备了一组已经初始化了的对象『池』而不是按需创建或者销毁的创建型设计模式。
 * 对象池的客户端会向对象池中请求一个对象，然后使用这个返回的对象执行相关操作。当客户端使用完毕
 * ，它将把这个特定类型的工厂对象返回给对象池，而不是销毁掉这个对象。
 *
 * 在初始化实例成本高，实例化率高，可用实例不足的情况下，对象池可以极大地提升性能。在创建对象
 * （尤其是通过网络）时间花销不确定的情况下，通过对象池在可期时间内就可以获得所需的对象。
 *
 * 无论如何，对象池模式在需要耗时创建对象方面，例如创建数据库连接，套接字连接，线程和大型图形对象
 * （比方字体或位图等），使用起来都是大有裨益的。在某些情况下，简单的对象池（无外部资源，只占内存）
 * 可能效率不高，甚至会有损性能
 *
 *
 * Object Pool，即对象池，对象被预先创建并初始化后放入对象池中，对象提供者就能利用已有的对象来处理请求，减少对象频繁创建所占用的内存空间和初始化时间，例如数据库连接对象基本上都是创建后就被放入连接池中，后续的查询请求使用的是连接池中的对象，从而加快了查询速度。类似被放入对象池中的对象还包括Socket对象、线程对象和绘图对象（GDI对象）等。
 * 在Object Pool设计模式中，主要有两个参与者：对象池的管理者和对象池的用户，用户从管理者那里获取对象，对象池对于用户来讲是透明的，但是用户必须遵守这些对象的使用规则，使用完对象后必须归还或者关闭对象，例如数据库连接对象使用完后必须关闭，否则该对象就会被一直占用着。
 * 对象管理者需要维护对象池，包括初始化对象池、扩充对象池的大小、重置归还对象的状态等。
 * 对象池在被初始化时，可能只有几个对象，甚至没有对象，按需创建对象能节省资源和时间，对于响应时间要求较高的情况，可以预先创建若干个对象。
 * 当对象池中没有对象可供使用时，管理者一般需要使用某种策略来扩充对象池，比如将对象池的大小翻倍。另外，在多线程的情况下，可以让请求资源的线程等待，直到其他线程归还了占用的对象。
 * 一般来说，对象池中的对象在逻辑状态上是相同的，如果都是无状态对象（即没有成员变量的对象），那么这些对象的管理会方便的多，否则，对象被使用后的状态重置工作就要由管理者来承担。
 */
namespace DesignPatterns\Creational\Pool;

class WorkerPool implements \Countable
{

    /**
     *
     * @var StringReverseWorker[]
     */
    private $occupiedWorkers = [];

    /**
     *
     * @var StringReverseWorker[]
     */
    private $freeWorkers = [];

    public function get(): StringReverseWorker
    {
        if (count($this->freeWorkers) == 0) {
            $worker = new StringReverseWorker();
        } else {
            $worker = array_pop($this->freeWorkers);
        }
        
        $this->occupiedWorkers[spl_object_hash($worker)] = $worker;
        
        return $worker;
    }

    public function dispose(StringReverseWorker $worker)
    {
        $key = spl_object_hash($worker);
        
        if (isset($this->occupiedWorkers[$key])) {
            unset($this->occupiedWorkers[$key]);
            $this->freeWorkers[$key] = $worker;
        }
    }

    public function count(): int
    {
        return count($this->occupiedWorkers) + count($this->freeWorkers);
    }
}

class StringReverseWorker
{

    /**
     *
     * @var \DateTime
     */
    private $createdAt;

    public function __construct()
    {
        $this->createdAt = new \DateTime();
    }

    public function run(string $text)
    {
        return strrev($text);
    }
}

$pool = new WorkerPool();
$worker1 = $pool->get();
$pool->dispose($worker1);
$worker2 = $pool->get();

$pool = new WorkerPool();
$worker1 = $pool->get();
$worker2 = $pool->get();